package com.yeziji.security.component;

import com.yeziji.common.CommonErrorMsg;
import com.yeziji.common.CommonResult;
import com.yeziji.common.context.OnlineContext;
import com.yeziji.utils.ResponseUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 动态权限校验拦截器
 *
 * @author hwy
 * @since 2023/11/12 22:02
 **/
public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter {
    /**
     * 拦截器标记，用于记录当前请求是否被拦截过
     */
    private static final String FILTER_TAG = "DynamicSecurityFilter#AbstractSecurityInterceptor#Filter";

    @Autowired
    private DynamicSecurityMetadataSource dynamicSecurityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
        super.setAccessDecisionManager(accessDecisionManager);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 已经通过一次拦截器就直接忽略
        if (servletRequest.getAttribute(FILTER_TAG) != null || OnlineContext.isAdmin()) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        } else {
            servletRequest.setAttribute(FILTER_TAG, true);
        }

        // 进行下一步校验
        FilterInvocation filterInvocation = new FilterInvocation(servletRequest, servletResponse, filterChain);
        if (!this.checkResponseStatus(filterInvocation.getResponse())) {
            return;
        }
        // 此处会调用 AccessDecisionManager 中的 decide 方法进行鉴权操作
        InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
        try {
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return dynamicSecurityMetadataSource;
    }

    /**
     * 检查响应结果
     *
     * @param response 响应
     * @return {@link Boolean} 为 false 说明检验失败
     */
    private boolean checkResponseStatus(HttpServletResponse response) {
        int status = response.getStatus();
        if (status == HttpStatus.NOT_FOUND.value()) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            ResponseUtils.writeJson(response, CommonResult.failed(CommonErrorMsg.PAGE_NOT_FOUND));
            return false;
        }
        return true;
    }
}
